#include "znx_listener.h"

znx_listeners_t                 *g_listeners;


void
znx_g_listeners_init()
{
    g_listeners = znx_thread_calloc(sizeof(znx_listeners_t));
    znx_rwlock_init(&g_listeners->rwlock);
    znx_queue_init(&g_listeners->head);
}


void
znx_g_listeners_close()
{
    znx_queue_t                 *q;
    znx_listener_t              *l;

    znx_rwlock_wlock(&g_listeners->rwlock);
    while (!znx_queue_empty(&g_listeners->head)) {
        q = znx_queue_last(&g_listeners->head);
        znx_queue_remove(q);
        l = znx_queue_data(q, znx_listener_t, queue);
        znx_listener_free(l);
    }
    znx_rwlock_wunlock(&g_listeners->rwlock);
}


static znx_bool_t
znx_listener_set_sockopt(znx_listener_t *l)
{
    znx_result_t            ret;

    ret = znx_syscall_set_nonblock(l->sockfd);
    if (ret < 0) {
        ZNX_DEF_LOG_ERROR(NULL, "setnonblock %s failed, errno: %d", l->listen_addr, -ret);
        return ZNX_FALSE;
    }

    return ZNX_TRUE;
}


znx_bool_t
znx_listener_open(znx_listener_t *l, int fd)
{
    int opt_val = 1;
    znx_result_t            ret;

    if (fd != INVALID_FD) {
        l->sockfd = fd;
        znx_listener_set_sockopt(l);
        return ZNX_TRUE;
    }

    ret = znx_syscall_socket(l->address.sockaddr.sa_family, l->type, 0);
    if (ret < 0) {
        ZNX_DEF_LOG_ERROR(NULL, "socket() %s failed, errno: %d", l->listen_addr, -ret);
        return ZNX_FALSE;
    }
    l->sockfd = (int)ret;

    if (l->reuseport) {
        ret = znx_syscall_setsockopt(l->sockfd, SOL_SOCKET, SO_REUSEPORT,
            &opt_val, sizeof(int));
        if (ret < 0) {
            ZNX_DEF_LOG_ERROR(NULL, "setsockopt(SO_REUSEPORT) %s failed, errno: %d",
                l->listen_addr, -ret);
            goto failed;
        }
    }

    if (l->reuseaddr) {
        ret = znx_syscall_setsockopt(l->sockfd, SOL_SOCKET, SO_REUSEADDR,
            &opt_val, sizeof(int));
        if (ret < 0) {
            ZNX_DEF_LOG_ERROR(NULL, "setsockopt(SO_REUSEADDR) %s failed, errno: %d",
                l->listen_addr, -ret);
            goto failed;
        }
    }

    if (l->ipv6only && l->address.sockaddr.sa_family == AF_INET6) {
        ret = znx_syscall_setsockopt(l->sockfd, IPPROTO_IPV6, IPV6_V6ONLY,
            &opt_val, sizeof(int));
        if (ret < 0) {
            ZNX_DEF_LOG_ERROR(NULL, "setsockopt(IPV6_V6ONLY) %s failed, errno: %d",
                l->listen_addr, -ret);
            goto failed;
        }
    }

    ret = znx_syscall_set_nonblock(l->sockfd);
    if (ret < 0) {
        ZNX_DEF_LOG_ERROR(NULL, "setnonblock %s failed, errno: %d", l->listen_addr, -ret);
        goto failed;
    }

#if 0
    ret = znx_syscall_set_close_exec(l->sockfd);
    if (ret < 0) {
        ZNX_DEF_LOG_ERROR(NULL, "setcloseexec %s failed, errno: %d", l->listen_addr, -ret);
        goto failed;
    }
#endif

    if (l->type != SOCK_STREAM) {
        return ZNX_TRUE;
    }

    ret = znx_syscall_bind(l->sockfd, &l->address.sockaddr,
        (socklen_t)znx_address_socklen(&l->address));
    if (ret < 0) {
        ZNX_DEF_LOG_ERROR(NULL, "bind %s failed, errno: %d", l->listen_addr, -ret);
        goto failed;
    }

    if (l->address.sockaddr.sa_family == AF_UNIX) {
        mode_t mode = (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
        if (chmod(l->address.sockaddr_un.sun_path, mode) == -1) {
            ZNX_DEF_LOG_ERROR(NULL, "chmod unix: %s failed, errno: %d",
                l->address.sockaddr_un.sun_path, errno);
            goto failed;
        }
    }

    ret = znx_syscall_listen(l->sockfd, l->backlog);
    if (ret < 0) {
        ZNX_DEF_LOG_ERROR(NULL, "listen() to %s failed, errno: %d", l->listen_addr, -ret);
        goto failed;
    }

    return ZNX_TRUE;

failed:

    znx_syscall_close(fd);
    return ZNX_FALSE;
}


void
znx_listener_close(znx_listener_t *l)
{
    if (l->sockfd == INVALID_FD) {
        return;
    }

    if (znx_atomic_bool_is_true(&l->handover)) {
        return;
    }

    znx_syscall_close(l->sockfd);

    l->closed = 1;
}

